"""
安全服务
处理API速率限制、输入验证等安全相关功能
"""

import time
import hashlib
from typing import Dict, Optional, Any, Callable, List, Set
from functools import wraps
from fastapi import Request, HTTPException, status, Response
from pydantic import ValidationError
import re
import html
import secrets
import string
from datetime import datetime, timedelta
import jwt
from src.config.settings import settings
from src.exceptions import RateLimitError, ValidationError as AppValidationError

# 尝试导入Redis用于分布式速率限制
try:
    import redis
    REDIS_AVAILABLE = True
except ImportError:
    REDIS_AVAILABLE = False

class RateLimiter:
    """基于内存的速率限制器"""
    
    def __init__(self):
        # 存储每个客户端的请求记录 {client_key: [timestamp1, timestamp2, ...]}
        self.requests: Dict[str, list] = {}
        # 存储每个客户端的限制配置 {client_key: {"limit": int, "window": int}}
        self.limits: Dict[str, dict] = {}
        # 尝试连接Redis
        self.redis_client: Optional[Any] = None
        if REDIS_AVAILABLE and getattr(settings, 'REDIS_URL', None):
            try:
                self.redis_client = redis.from_url(settings.REDIS_URL)
            except Exception:
                self.redis_client = None
    
    def set_limit(self, client_key: str, limit: int, window: int):
        """
        为客户端设置速率限制
        
        Args:
            client_key: 客户端标识符
            limit: 时间窗口内的最大请求数
            window: 时间窗口（秒）
        """
        self.limits[client_key] = {
            "limit": limit,
            "window": window
        }
    
    def is_allowed(self, client_key: str) -> bool:
        """
        检查客户端是否被允许发起请求
        
        Args:
            client_key: 客户端标识符
            
        Returns:
            bool: True表示允许，False表示被限制
        """
        current_time = time.time()
        
        # 如果没有设置限制，则允许请求
        if client_key not in self.limits:
            return True
            
        limit_config = self.limits[client_key]
        limit = limit_config["limit"]
        window = limit_config["window"]
        
        # 如果有Redis连接，使用Redis进行分布式速率限制
        if self.redis_client:
            try:
                # 使用Redis的有序集合存储请求时间戳
                key = f"rate_limit:{client_key}"
                now = time.time()
                pipeline = self.redis_client.pipeline()
                
                # 移除过期的请求记录
                pipeline.zremrangebyscore(key, 0, now - window)
                
                # 获取当前请求数
                pipeline.zcard(key)
                
                # 添加当前请求
                pipeline.zadd(key, {str(now): now})
                
                # 设置过期时间
                pipeline.expire(key, int(window))
                
                results = pipeline.execute()
                current_requests = results[1]
                
                return current_requests < limit
            except Exception:
                # Redis出错时回退到内存实现
                pass
        
        # 初始化客户端请求记录
        if client_key not in self.requests:
            self.requests[client_key] = []
        
        # 清理过期的请求记录
        self.requests[client_key] = [
            req_time for req_time in self.requests[client_key]
            if current_time - req_time < window
        ]
        
        # 检查是否超过限制
        if len(self.requests[client_key]) >= limit:
            return False
        
        # 记录当前请求
        self.requests[client_key].append(current_time)
        return True
    
    def get_retry_after(self, client_key: str) -> int:
        """
        获取需要等待的时间（秒）
        
        Args:
            client_key: 客户端标识符
            
        Returns:
            int: 需要等待的秒数
        """
        if client_key not in self.requests or client_key not in self.limits:
            return 0
            
        current_time = time.time()
        window = self.limits[client_key]["window"]
        
        # 如果有Redis连接，从Redis获取信息
        if self.redis_client:
            try:
                key = f"rate_limit:{client_key}"
                earliest_request = self.redis_client.zrange(key, 0, 0, withscores=True)
                if earliest_request:
                    earliest_time = float(earliest_request[0][1]) if isinstance(earliest_request[0], (list, tuple)) else float(earliest_request[0])
                    return int(earliest_time + window - current_time)
                return 0
            except Exception:
                pass
        
        # 找到最早的请求时间
        if self.requests[client_key]:
            earliest_request = min(self.requests[client_key])
            return int(earliest_request + window - current_time)
        
        return 0


# 全局速率限制器实例
rate_limiter = RateLimiter()


def get_client_key(request: Request) -> str:
    """
    获取客户端唯一标识符
    
    Args:
        request: FastAPI请求对象
        
    Returns:
        str: 客户端标识符
    """
    # 使用IP地址和User-Agent作为客户端标识符
    client_ip = request.client.host if request.client else "unknown"
    user_agent = request.headers.get("user-agent", "unknown")
    
    # 创建哈希值作为客户端标识符
    client_string = f"{client_ip}:{user_agent}"
    return hashlib.md5(client_string.encode()).hexdigest()


def rate_limit(limit: int = 60, window: int = 60):
    """
    API速率限制装饰器
    
    Args:
        limit: 时间窗口内的最大请求数
        window: 时间窗口（秒）
    """
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            # 在FastAPI中，request通常作为第一个参数或在kwargs中
            request = kwargs.get('request')
            if request is None:
                for arg in args:
                    if isinstance(arg, Request):
                        request = arg
                        break
            
            if request is None:
                # 如果找不到request对象，跳过速率限制
                return await func(*args, **kwargs)
            
            # 获取客户端标识符
            client_key = get_client_key(request)
            
            # 设置速率限制
            rate_limiter.set_limit(client_key, limit, window)
            
            # 检查是否允许请求
            if not rate_limiter.is_allowed(client_key):
                retry_after = rate_limiter.get_retry_after(client_key)
                raise RateLimitError(limit=limit, window=f"{window}秒")
            
            return await func(*args, **kwargs)
        return wrapper
    return decorator


class InputValidator:
    """输入验证器"""
    
    @staticmethod
    def sanitize_string(value: str) -> str:
        """
        清理字符串输入，防止XSS攻击
        
        Args:
            value: 输入字符串
            
        Returns:
            str: 清理后的字符串
        """
        # HTML转义
        sanitized = html.escape(value)
        # 移除潜在的危险字符
        sanitized = re.sub(r'[<>"\']', '', sanitized)
        return sanitized
    
    @staticmethod
    def validate_email(email: str) -> bool:
        """
        验证邮箱格式
        
        Args:
            email: 邮箱地址
            
        Returns:
            bool: 验证结果
        """
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        return re.match(pattern, email) is not None
    
    @staticmethod
    def validate_phone(phone: str) -> bool:
        """
        验证电话号码格式
        
        Args:
            phone: 电话号码
            
        Returns:
            bool: 验证结果
        """
        # 支持多种电话号码格式
        patterns = [
            r'^\+?1?[ -]?\(?[0-9]{3}\)?[ -]?[0-9]{3}[ -]?[0-9]{4}$',  # 美国格式
            r'^\+?[1-9]\d{1,14}$',  # E.164格式
            r'^\d{10,11}$'  # 简单数字格式
        ]
        
        return any(re.match(pattern, phone) for pattern in patterns)
    
    @staticmethod
    def validate_length(value: str, min_length: int = 0, max_length: int = 255) -> bool:
        """
        验证字符串长度
        
        Args:
            value: 输入字符串
            min_length: 最小长度
            max_length: 最大长度
            
        Returns:
            bool: 验证结果
        """
        return min_length <= len(value) <= max_length
    
    @staticmethod
    def validate_alphanumeric(value: str) -> bool:
        """
        验证字符串是否只包含字母和数字
        
        Args:
            value: 输入字符串
            
        Returns:
            bool: 验证结果
        """
        return value.isalnum()
    
    @staticmethod
    def validate_no_sql_injection(value: str) -> bool:
        """
        检查是否存在SQL注入关键词
        
        Args:
            value: 输入字符串
            
        Returns:
            bool: 验证结果
        """
        if not isinstance(value, str):
            return True
            
        # SQL注入关键词列表（不区分大小写）
        sql_keywords = [
            'union', 'select', 'insert', 'update', 'delete', 'drop', 'create', 'alter',
            'exec', 'execute', 'script', 'javascript', 'vbscript', 'expression',
            'declare', 'cast', 'convert', 'waitfor', 'delay', 'benchmark', 'substring',
            'concat', 'extractvalue', 'updatexml', 'group_concat'
        ]
        
        # 常见的SQL注入模式
        sql_patterns = [
            r'(\'|\")\s*(or|and)\s*\d+=\d+',  # ' or 1=1
            r'[^\w](union|select|insert|update|delete|drop|create|alter)[^\w]',  # 关键词注入
            r'(\||&&|&|<|>)',  # 命令注入符号
            r'(\/\*|\*\/|--|#)',  # 注释符号
            r'(sleep\s*\(|benchmark\s*\()',  # 时间延迟函数
        ]
        
        value_lower = value.lower().replace(' ', '')
        for keyword in sql_keywords:
            # 检查关键词是否在输入中
            if keyword in value_lower:
                return False
                
        # 检查模式
        import re
        for pattern in sql_patterns:
            if re.search(pattern, value, re.IGNORECASE):
                return False
                
        return True


def validate_input(**validators):
    """
    输入验证装饰器
    
    Args:
        **validators: 验证规则字典，格式为 {参数名: [验证函数列表]}
    """
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            # 验证kwargs中的参数
            for param_name, validation_rules in validators.items():
                if param_name in kwargs:
                    value = kwargs[param_name]
                    
                    # 如果值是字符串，先进行清理
                    if isinstance(value, str):
                        value = InputValidator.sanitize_string(value)
                        kwargs[param_name] = value
                    
                    # 执行验证规则
                    for rule in validation_rules:
                        if callable(rule):
                            if not rule(value):
                                raise AppValidationError(
                                    message=f"参数 {param_name} 验证失败",
                                    field=param_name
                                )
            
            return await func(*args, **kwargs)
        return wrapper
    return decorator


# 安全增强功能
class SecurityEnhancer:
    """安全增强功能类"""
    
    @staticmethod
    def generate_secure_token(length: int = 32) -> str:
        """生成安全的随机令牌"""
        alphabet = string.ascii_letters + string.digits
        return ''.join(secrets.choice(alphabet) for _ in range(length))
    
    @staticmethod
    def setup_security_headers(response: Response) -> Response:
        """设置安全相关的HTTP头"""
        # 内容安全策略
        response.headers["Content-Security-Policy"] = (
            "default-src 'self'; "
            "script-src 'self' 'unsafe-inline'; "
            "style-src 'self' 'unsafe-inline'; "
            "img-src 'self' data: https:; "
            "connect-src 'self'; "
            "font-src 'self' data:; "
            "object-src 'none'; "
            "media-src 'self'; "
            "frame-ancestors 'none'; "
            "base-uri 'self';"
        )
        
        # 其他安全头
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["X-Frame-Options"] = "DENY"
        response.headers["X-XSS-Protection"] = "1; mode=block"
        response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
        response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
        response.headers["Permissions-Policy"] = "geolocation=(), microphone=(), camera=()"
        
        # 确保返回的是Response对象
        return response
    
    @staticmethod
    def validate_csrf_token(request: Request, expected_token: str) -> bool:
        """验证CSRF令牌"""
        # 从请求头或表单数据中获取CSRF令牌
        csrf_token = None
        if hasattr(request, 'headers') and hasattr(request.headers, 'get'):
            csrf_token = request.headers.get("X-CSRF-Token")
        
        if not csrf_token and hasattr(request, 'form'):
            form_data = request.form()
            if hasattr(form_data, 'get'):
                csrf_token = form_data.get("csrf_token")
                
        return csrf_token == expected_token and csrf_token is not None


# 密码安全工具
class PasswordSecurity:
    """密码安全相关工具"""
    
    @staticmethod
    def validate_password_strength(password: str) -> dict:
        """验证密码强度"""
        result = {
            "is_strong": True,
            "issues": []
        }
        
        # 长度检查
        if len(password) < 8:
            result["is_strong"] = False
            result["issues"].append("密码长度至少8个字符")
        
        # 复杂度检查
        has_upper = any(c.isupper() for c in password)
        has_lower = any(c.islower() for c in password)
        has_digit = any(c.isdigit() for c in password)
        has_special = any(not c.isalnum() for c in password)
        
        if not has_upper:
            result["is_strong"] = False
            result["issues"].append("密码应包含大写字母")
        if not has_lower:
            result["is_strong"] = False
            result["issues"].append("密码应包含小写字母")
        if not has_digit:
            result["is_strong"] = False
            result["issues"].append("密码应包含数字")
        if not has_special:
            result["is_strong"] = False
            result["issues"].append("密码应包含特殊字符")
        
        # 常见密码检查
        common_passwords = {"password", "123456", "qwerty", "admin", "welcome", "password123"}
        if password.lower() in common_passwords:
            result["is_strong"] = False
            result["issues"].append("密码过于常见")
        
        # 检查字符重复
        if any(password.count(c) > 3 for c in set(password)):
            result["is_strong"] = False
            result["issues"].append("密码中单个字符重复次数过多")
        
        return result
    
    @staticmethod
    def generate_secure_password(length: int = 12) -> str:
        """生成安全密码"""
        if length < 12:
            length = 12
        
        # 确保包含各种字符类型
        upper = secrets.choice(string.ascii_uppercase)
        lower = secrets.choice(string.ascii_lowercase)
        digit = secrets.choice(string.digits)
        special = secrets.choice("!@#$%^&*()_+-=[]{}|;:,.<>?")
        
        # 生成剩余字符
        remaining = length - 4
        all_chars = string.ascii_letters + string.digits + "!@#$%^&*()_+-=[]{}|;:,.<>?"
        rest = ''.join(secrets.choice(all_chars) for _ in range(remaining))
        
        # 组合并随机打乱
        password_chars = list(upper + lower + digit + special + rest)
        secrets.SystemRandom().shuffle(password_chars)
        
        return ''.join(password_chars)


# 全局安全工具实例
security_enhancer = SecurityEnhancer()
password_security = PasswordSecurity()
